package com.gugee.tiktok.web.service;

import cn.hutool.core.date.DateUtil;
import com.gugee.common.web.util.ESUtil;
import com.gugee.common.web.util.RedisKeysManager;
import com.gugee.common.web.util.Ret;
import com.gugee.tiktok.web.dto.*;
import com.gugee.tiktok.web.model.mysql.*;
import com.gugee.tiktok.web.repository.*;
import com.xiguaji.boot.toolkit.JsonUtil;
import com.xiguaji.boot.toolkit.jdbc.Page;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class BloggerService {

    @Autowired
    BloggerRepository bloggerRepository;

    @Autowired
    AwemeRepository awemeRepository;

    @Autowired
    RestHighLevelClient client;

    @Autowired
    BloggerTypeRepository bloggerTypeRepository;

    @Autowired
    BloggerCollectionRepository bloggerCollectionRepository;

    @Autowired
    BloggerResponse.BloggerMapper bloggerMapper;

    @Autowired
    BloggerRecordRepository bloggerRecordRepository;

    @Autowired
    BloggerPerformanceResponse.BloggerMapper bloggerPerformanceMapper;

    @Autowired
    AwemeCollectionRepository awemeCollectionRepository;

    /**
     * 排行版服务
     */
    @Autowired
    RankingService rankingService;

    /**
     * redis服务
     */
    @Autowired
    RedisTemplate<String,String> redisTemplate;

    /**
     * rest服务
     */
    @Autowired
    RestTemplate restTemplate;

    /**
     * 粉丝画像 Dao
     */
    @Autowired
    BloggerFansPortraitRepository bloggerFansPortraitRepository;

    /**
     * 星图dao
     */
    @Autowired
    BloggerMarketRepository bloggerMarketRepository;

    /**
     * 语言dao
     */
    @Autowired
    DictionaryService dictionaryService;

    /**
     * 地区dao
     */
    @Autowired
    RegionRepository regionRepository;

    double [] followers = new double[]{10,100,500,1000,5000,10000};
    double [] views = new double[]{10,100,500,1000,2000,5000};
    double [] likes = new double[]{10,50,100,500,1000,2000};
    double [] favorites = new double[]{100,1000,10000,50000,100000,500000};

    double [] reviews = new double[]{0.5,1,5,10,50,100};
    double [] shares = new double[]{0.5,1,5,10,50,100};
    double [] gd = new double[]{200,300,500,700,900,1000};
    int [] ages = new int[]{18,24,34,35};
    int [] genders = new int[]{3,2};
    String [] audienceGenders = new String[]{"MALE","FEMALE"};
    String [] audienceAges = new String[]{"18-","18-24","25-34","35-44","45+"};
    Map<String,String> languages;
    Map<String,String> regions;

    @PostConstruct
    public void postConstruct(){
        languages = dictionaryService.getDicLanguageMap();
        regions = dictionaryService.getDicRegionMap();
    }


    /**
     * tiktok-data-web接口服务地址
     */
    @Value("${tiktok.data.web.host}")
    private String TIKTOK_DATA_WEB_HOST;

    /**
     * 发送更新播主信息到kafka队列的间隔时间（redis）
     */
    @Value("${tiktok.blogger.updatemark.expired.day}")
    private int BLOGGER_UPDATE_KEY_EXPIRED;

    /**
     * 同一播主发送更新信息到kafka队列时分布式锁的过期时间（redis）
     */
    @Value("${tiktok.blogger.callupdatemark.expired.second}")
    private int CALL_BLOGGER_UPDATE_KEY_EXPIRED;

    /**
     * 博主粉丝画像记录的redis 过期时间
     */
    @Value("${tiktok.blogger.fans.portrait.expired.day}")
    private int BLOGGER_FANS_PORT_KEY_EXPIRED;

    /**
     * 博主粉丝画像记录（查询为空时）的redis 过期时间
     */
    @Value("${tiktok.blogger.fans.portrait.temp.expired.hour}")
    private int BLOGGER_FANS_PORT_EMPTY_KEY_EXPIRED;

    /**
     * 博主粉丝画像记录查询锁的过期时间
     */
    @Value("${tiktok.blogger.fans.portrait.lock.expired.second}")
    private int BLOGGER_FANS_PORT_LOCK_KEY_EXPIRED;

    /**
     * 粉丝画像统计有效视频的区间天数
     */
    @Value("${tiktok.blogger.fans.portrait.statistics.day.limit}")
    private int FANS_PORTRAIT_STATISTICS_DAY_LIMIT;

    /**
     * 粉丝画像统计语言分布top个数
     */
    @Value("${tiktok.blogger.fans.portrait.statistics.language.top}")
    private int FANS_PORTRAIT_STATISTICS_LANGUAGE_TOP;

    /**
     * 粉丝画像统计国家分布top个数
     */
    @Value("${tiktok.blogger.fans.portrait.statistics.region.top}")
    private int FANS_PORTRAIT_STATISTICS_REGION_TOP;

    public String getPageByEs(BloggerSearchData searchData, Long userId, Long roleId) throws IOException {
        SearchRequest searchRequest = new SearchRequest(ESUtil.BLOGGER_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if(!StringUtils.isEmpty(searchData.getUserName())){
            boolQueryBuilder.should(QueryBuilders.wildcardQuery("nickName","*"+searchData.getUserName().toLowerCase()+"*"));
            boolQueryBuilder.should(QueryBuilders.matchQuery("nickName",searchData.getUserName()));
            boolQueryBuilder.should(QueryBuilders.wildcardQuery("uniqueId","*"+searchData.getUserName().toLowerCase()+"*"));
            boolQueryBuilder.should(QueryBuilders.matchQuery("uniqueId",searchData.getUserName()));
            boolQueryBuilder.minimumShouldMatch(1);
        }

        if(!StringUtils.isEmpty(searchData.getCategory())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("bloggerType",searchData.getCategory()));
        }

        if(null != searchData.getFollowers() && searchData.getFollowers() > 0){
            int index = searchData.getFollowers() -1;
            if(index == 0){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("followerCount").lt(followers[index]*1000));
            }else if(index >= followers.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("followerCount").gte(followers[followers.length-1]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("followerCount").gte(followers[index-1]*1000).lte(followers[index]*1000));
            }
        }
        if(null != searchData.getViews() && searchData.getViews() > 0){
            int index = searchData.getViews() -1;
            if(index == 0){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgViews").lt(views[index]*1000));
            }else if(index >= views.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgViews").gte(views[views.length-1]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgViews").gte(views[index-1]*1000).lte(views[index]*1000));
            }
        }
        if(null != searchData.getReviews() && searchData.getReviews() > 0){
            int index = searchData.getReviews() -1;
            if(index == 0){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgReviews").lt(reviews[index]*1000));
            }else if(index >= reviews.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgReviews").gte(reviews[reviews.length-1]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgReviews").gte(reviews[index-1]*1000).lte(reviews[index]*1000));
            }
        }
        if(null != searchData.getLikes() && searchData.getLikes() > 0){
            int index = searchData.getLikes() -1;
            if(index == 0){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgLikes").lt(likes[index]*1000));
            }else if(index >= likes.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgLikes").gte(likes[likes.length-1]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgLikes").gte(likes[index-1]*1000).lte(likes[index]*1000));
            }
        }
        if(null != searchData.getShare() && searchData.getShare() > 0){
            int index = searchData.getShare() -1;
            if(index == 0){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgShare").lt(shares[index]*1000));
            }else if(index >= shares.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgShare").gte(shares[shares.length-1]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("avgShare").gte(shares[index-1]*1000).lte(shares[index]*1000));
            }
        }
        if(null != searchData.getGd() && searchData.getGd() > 0){
            int index = searchData.getGd()-1;

            if(index >= gd.length-1){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("score").gt(gd[index]));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("score").gte(gd[index]).lte(gd[index+1]));
            }
        }

        if(null != searchData.getFavorite() && searchData.getFavorite() > 0){
            int index = searchData.getFavorite()-1;

            if(index >= favorites.length-1){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("totalFavorited").gt(favorites[index]*1000));
            }else{
                boolQueryBuilder.must(QueryBuilders.rangeQuery("totalFavorited").gte(favorites[index]*1000).lte(favorites[index+1]*1000));
            }
        }

        if(searchData.getEnterpriseVerify() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.matchQuery("enterpriseVerifyReason",""));
            boolQueryBuilder.must(QueryBuilders.existsQuery("enterpriseVerifyReason"));
        }

        if(searchData.getIndividualVerify() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.matchQuery("customVerify",""));
            boolQueryBuilder.must(QueryBuilders.existsQuery("customVerify"));
        }

        if(searchData.getCommerce() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.matchQuery("withFusionShopEntry",false));
            boolQueryBuilder.must(QueryBuilders.existsQuery("withFusionShopEntry"));
        }

        if(searchData.getEmail() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.matchQuery("bioEmail",""));
            boolQueryBuilder.must(QueryBuilders.existsQuery("bioEmail"));
        }
        if(searchData.getWebsite() == 1){
            boolQueryBuilder.mustNot(QueryBuilders.matchQuery("bioUrl",""));
            boolQueryBuilder.must(QueryBuilders.existsQuery("bioUrl"));
        }



        if(!StringUtils.isEmpty(searchData.getRegion())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("region",searchData.getRegion()));
        }


        if(searchData.getAge() > 0){
            int index = searchData.getAge()-1;

            if(index == ages.length-1){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("aiAge").gt(ages[index]));
            }else if(index > ages.length){
                boolQueryBuilder.must(QueryBuilders.rangeQuery("aiAge").lt(ages[0]));
            }else {
                boolQueryBuilder.must(QueryBuilders.rangeQuery("aiAge").gte(ages[index]).lte(ages[index+1]));
            }
            boolQueryBuilder.must(QueryBuilders.matchQuery("aiAge",ages[searchData.getAge()-1]));
        }

        if(searchData.getGender() > 0){
            int index = searchData.getGender()-1;

            if(index >= genders.length){
                boolQueryBuilder.must(QueryBuilders.matchQuery("aiGender",genders[genders.length-1]));
            }
            boolQueryBuilder.must(QueryBuilders.matchQuery("aiGender",genders[index]));
        }

        if(searchData.getAudienceAgeMore() > 0){
            boolQueryBuilder.must(QueryBuilders.matchQuery("ageDistributionMax",audienceAges[searchData.getAudienceAgeMore()-1]));
        }
        if(searchData.getAudienceGenderMore() > 0){
            boolQueryBuilder.must(QueryBuilders.matchQuery("genderDistributionMax",audienceGenders[searchData.getAudienceGenderMore()-1]));
        }
        if(!StringUtils.isEmpty(searchData.getAudienceLanguageMore())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("languageDistributionMax",languages.get(searchData.getAudienceLanguageMore())));
        }
        if(!StringUtils.isEmpty(searchData.getAudienceRegionMore())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("regionDistributionMax",regions.get(searchData.getAudienceRegionMore().toLowerCase())));
        }

        if(null != searchData.getAudienceAgeData() && searchData.getAudienceAgeData().getProperty() > 0){
            BoolQueryBuilder ageQuery = new BoolQueryBuilder();
            ageQuery.must(QueryBuilders.matchQuery("ageDistribution.property",audienceAges[searchData.getAudienceAgeData().getProperty()-1]));
            ageQuery.must(QueryBuilders.rangeQuery("ageDistribution.value").gte(searchData.getAudienceAgeData().getValue()));
            boolQueryBuilder.must(QueryBuilders.nestedQuery("ageDistribution",ageQuery, ScoreMode.Total));
        }

        if(0 != searchData.getAudienceGenderMore() && searchData.getAudienceGenderPercent() > 0){

            BoolQueryBuilder genderQuery = new BoolQueryBuilder();
            genderQuery.must(QueryBuilders.termQuery("genderDistribution.property",audienceGenders[searchData.getAudienceGenderMore()-1]));
            genderQuery.must(QueryBuilders.rangeQuery("genderDistribution.value").gte(searchData.getAudienceGenderPercent()));
            boolQueryBuilder.must(QueryBuilders.nestedQuery("genderDistribution",genderQuery, ScoreMode.Total));
        }

        if(null != searchData.getAudienceLanguageData() && !StringUtils.isEmpty(searchData.getAudienceLanguageData().getProperty())){
            BoolQueryBuilder languageQuery = new BoolQueryBuilder();
            languageQuery.must(QueryBuilders.matchQuery("languageDistribution.property",languages.get(searchData.getAudienceLanguageData().getProperty())));
            languageQuery.must(QueryBuilders.rangeQuery("languageDistribution.value").gte(searchData.getAudienceLanguageData().getValue()));
            boolQueryBuilder.must(QueryBuilders.nestedQuery("languageDistribution",languageQuery, ScoreMode.Total));
        }

        if(null != searchData.getAudienceRegionData() && !StringUtils.isEmpty(searchData.getAudienceRegionData().getProperty())){
            BoolQueryBuilder regionQuery = new BoolQueryBuilder();
            regionQuery.must(QueryBuilders.matchQuery("regionDistribution.property",regions.get(searchData.getAudienceRegionData().getProperty())));
            regionQuery.must(QueryBuilders.rangeQuery("regionDistribution.value").gte(searchData.getAudienceRegionData().getValue()));
            boolQueryBuilder.must(QueryBuilders.nestedQuery("regionDistribution",regionQuery, ScoreMode.Total));
        }


        String sort = "avgViews";

        switch (searchData.getSort()){
            case 1:
                sort = "score";
                break;
            case 2:
                sort = "followerCount";
                break;
            case 3:
                sort = "avgViews";
                break;
            case 4:
                sort = "avgLikes";
                break;
            case 5:
                sort = "avgReviews";
                break;
            case 6:
                sort = "avgShare";
                break;
            case 7:
                sort = "totalFavorited";
                break;
            default:
                break;
        }
        searchSourceBuilder.query(boolQueryBuilder);
        searchSourceBuilder.sort(sort, SortOrder.DESC);
        searchSourceBuilder.from((searchData.getPage()-1)*searchData.getPageSize());
        searchSourceBuilder.size(searchData.getPageSize());
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        val value = searchResponse.getHits().getTotalHits().value;
        val hits = searchResponse.getHits().getHits();
        List<BloggerResponse> bloggerResponses = new ArrayList<>();
        for (SearchHit hit : hits) {
            BloggerResponse bloggerResponse = JsonUtil.jsonParse(BloggerResponse.class,hit.getSourceAsString());
            bloggerResponse.setNickname(hit.getSourceAsMap().get("nickName").toString());
            val bloggerCollectionOptional = bloggerCollectionRepository.findByBloggerIdAndUserId(bloggerResponse.getUid(), userId);

            if(bloggerCollectionOptional.isPresent()){
                bloggerResponse.setFavorite(true);
                bloggerResponse.setCollectId(bloggerCollectionOptional.get().getId());
            }

            if(null != bloggerResponse.getAgeDistribution()) {
                bloggerResponse.setFansAge(bloggerResponse.getAgeDistribution().stream().max((a, b) -> a.getValue() > b.getValue() ? 1 : -1).get());
                bloggerResponse.setFansGender(bloggerResponse.getGenderDistribution().stream().max((a, b) -> a.getValue() > b.getValue() ? 1 : -1).get());
                bloggerResponse.setFansRegion(bloggerResponse.getRegionDistribution().stream().max((a, b) -> a.getValue() > b.getValue() ? 1 : -1).get());
                bloggerResponse.setFansLanguage(bloggerResponse.getLanguageDistribution().stream().max((a, b) -> a.getValue() > b.getValue() ? 1 : -1).get());
            }
            /*bloggerResponse.setAgeDistribution(null);
            bloggerResponse.setGenderDistribution(null);
            bloggerResponse.setRegionDistribution(null);
            bloggerResponse.setLanguageDistribution(null);*/
            bloggerResponses.add(bloggerResponse);
        }

        return Ret.pageData(value,bloggerResponses);
    }

    /*private BloggerRepository ESData2BloggerResponse(List<Map<String,Object>> maps){
        List<BloggerResponse> bloggerRespons

    }*/


    public List<BloggerType> getTypeList(){
        return bloggerTypeRepository.getBloggerTypeByIsDisplayOrderBySortAsc(true);
    }

    public BloggerResponse getBloggerInfo(long bloggerId,long userId){
        val blogger = bloggerRepository.findByOUid(bloggerId);
        val bloggerResponse = bloggerMapper.mapper(blogger.get());
        val bloggerCollectionOptional = bloggerCollectionRepository.findByBloggerIdAndUserId(bloggerId, userId);

        //设置播主当日全球排名
        int dateCode = Integer.parseInt(DateUtil.format(LocalDateTime.now().minusDays(1),"yyyyMMdd"));
        Ranking ranking = rankingService.getBloggerRanking(bloggerId, 1,0,dateCode);
        if(ranking != null){
            bloggerResponse.setGlobalRanking(ranking.getRanking());
        }

        //设置是否收藏
        if(bloggerCollectionOptional.isPresent()){
            bloggerResponse.setFavorite(true);
            bloggerResponse.setCollectId(bloggerCollectionOptional.get().getId());
        }
        return bloggerResponse;
    }

    public List<Blogger> getRandomBloggers(int size){
        Random r = new Random();
        int start = r.nextInt(4990);
        return bloggerRepository.findByCountSizeOrderByScore(start,size);
    }

    public Blogger getBloggerByUniqueId(String uniqueId){
        Optional<Blogger> bloggerOptional = bloggerRepository.findByoUniqueId(uniqueId);
        return bloggerOptional.isPresent()?bloggerOptional.get():null;
    }

    public BloggerDataResponse getBloggerData(long bloggerId, int day){
        long startTime = getStartDay();
        long endTime = getEndDay(startTime,day);

        BloggerDataResponse response = new BloggerDataResponse();
        // 当前期作品
        var recentPost = awemeRepository.countAwemeByOCreateTime(bloggerId,endTime,startTime);
        response.setRecentPosts(recentPost);
        // 上期作品
        long beforeStartTime = getStartDay(endTime);
        long beforeEndTime = getEndDay(endTime,day);
        var lastPost = awemeRepository.countByOUidAndOCreateTimeBetween(bloggerId,beforeEndTime,beforeStartTime);
        response.setLastPosts(lastPost);
        response.setPercentPosts(calculate(response.getRecentPosts(),response.getLastPosts()));
        // 当前期
        val recentGrowthRecord = bloggerRecordRepository.findByGroupData(bloggerId, getDayCode(endTime), getDayCode(startTime));

        if (null == recentGrowthRecord){
            return response;
        }

        response.setRecentLikesGrowth(recentGrowthRecord.getLikeGrowth());
        response.setRecentFollowerGrowth(recentGrowthRecord.getFollowerGrowth());
        response.setRecentViewGrowth(recentGrowthRecord.getViewGrowth());
        response.setRecentReviewGrowth(recentGrowthRecord.getReviewGrowth());
        response.setRecentShareGrowth(recentGrowthRecord.getShareGrowth());
        // 上期
        var lastGrowthRecord = bloggerRecordRepository.findByGroupData(bloggerId,getDayCode(getEndDay(endTime,day)),getDayCode(endTime));

        if(null != lastGrowthRecord){
            response.setLastFollowerGrowth(lastGrowthRecord.getFollowerGrowth());
            response.setLastViewGrowth(lastGrowthRecord.getViewGrowth());
            response.setLastReviewGrowth(lastGrowthRecord.getReviewGrowth());
            response.setLastLikesGrowth(lastGrowthRecord.getLikeGrowth());
            response.setLastShareGrowth(lastGrowthRecord.getShareGrowth());
            response.setPercentFollowerGrowth(calculate(response.getRecentFollowerGrowth(),response.getLastFollowerGrowth()));
            response.setPercentViewGrowth(calculate(response.getRecentViewGrowth(),response.getLastViewGrowth()));
            response.setPercentReviewGrowth(calculate(response.getRecentReviewGrowth(),response.getLastReviewGrowth()));
            response.setPercentLikesGrowth(calculate(response.getRecentLikesGrowth(),response.getLastLikesGrowth()));
            response.setPercentShareGrowth(calculate(response.getRecentShareGrowth(),response.getLastShareGrowth()));
        }
        return response;
    }

    private double calculate(long first,long last){
        try{
            val difference = new BigDecimal(first).subtract(new BigDecimal(last));
            val result = difference.divide(new BigDecimal(last),2, RoundingMode.FLOOR);
            return result.multiply(new BigDecimal(100)).setScale(2).doubleValue();
        }catch (Exception e){
            return 0;
        }
    }

    public List<BloggerPerformanceResponse> getBloggerPerformance(long bloggerId,int day){
        val startDay = getStartDay();
        val endDay = getEndDay(startDay, day);
        val bloggerPerformances = bloggerPerformanceMapper.mapper(getBloggerRecords(bloggerId, getDayCode(endDay), getDayCode(startDay)));
        return bloggerPerformances;

    }

    public List<AwemeResponse> getBloggerAwemePerformance(long bloggerId,int day){
        val startDay = getStartToDay();
        val endDay = getEndDay(startDay, day);
        val topAwemes = awemeRepository.findTopAwemes(bloggerId, endDay, startDay);
        Collections.reverse(topAwemes);
        return topAwemes;
    }

    public List<BloggerRecord> getBloggerRecords(long uid, int startDay, int lastDay){
        val bloggerRecordList = bloggerRecordRepository.findByUidAndDatecodeBetweenOrderByDatecodeDesc(uid, startDay, lastDay);
        Collections.reverse(bloggerRecordList);
        return bloggerRecordList;
    }

    public Page<BloggerAwemeResponse> getAwemePageByBlogger(long bloggerId, BloggerSearchData searchData, long userId){
        val startDay = getStartToDay();
        val endDay = getSearchBeforeDay(startDay,searchData.getInfoType());
        val page = awemeRepository.getPage(bloggerId, AwemeExtRepository.SearchType.BLOGGER,
                searchData.getPage(), searchData.getPageSize(),
                endDay,startDay, searchData.getSort());

        page.getList().forEach( a->{
            val awemeCollectionOptional = awemeCollectionRepository.findByAwemeIdAndUserId(a.getAwemeId(), userId);
            if(awemeCollectionOptional.isPresent()){
                a.setFavorite(true);
                a.setCollectId(awemeCollectionOptional.get().getId());
            }
        });
        return page;
    }

    public int getAwemeCountByBlogger(long bloggerId){
        val startDay = getStartToDay();
        val endDay = getSearchBeforeDay(startDay,2);
        return awemeRepository.countAwemeByOCreateTime(bloggerId,endDay,startDay);
    }

    private long getSearchBeforeDay(long daySecond,int searchType){
        switch (searchType){
            case 1:
                return getEndDay(daySecond,7);
            case 2:
                return getBeforeMonthDay(daySecond,1);
            case 3:
                return getBeforeMonthDay(daySecond,3);
            default:
                return getEndDay(daySecond,7);
        }
    }

    private long getStartDay(){
        val calendar = DateUtil.calendar();
        calendar.add(Calendar.DAY_OF_MONTH,-1);
        calendar.set(Calendar.HOUR_OF_DAY,23);
        calendar.set(Calendar.MINUTE,59);
        calendar.set(Calendar.SECOND,59);
        return calendar.getTimeInMillis()/1000;
    }
    private long getStartToDay(){
        val calendar = DateUtil.calendar();
        calendar.set(Calendar.HOUR_OF_DAY,23);
        calendar.set(Calendar.MINUTE,59);
        calendar.set(Calendar.SECOND,59);
        return calendar.getTimeInMillis()/1000;
    }

    private long getStartDay(long daySecond){
        val calendar =  DateUtil.calendar(daySecond*1000);
        calendar.set(Calendar.HOUR_OF_DAY,23);
        calendar.set(Calendar.MINUTE,59);
        calendar.set(Calendar.SECOND,59);
        return calendar.getTimeInMillis()/1000;
    }

    private long getBeforeMonthDay(long daySecond,int month){
        val calendar = DateUtil.calendar(daySecond * 1000);
        calendar.add(Calendar.MONTH,-month);
        return calendar.getTimeInMillis()/1000;
    }

    private long getEndDay(long daySecond,int day){
        val calendar = DateUtil.calendar(daySecond*1000);
        calendar.add(Calendar.DAY_OF_MONTH,-day);
        calendar.set(Calendar.HOUR_OF_DAY,0);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.SECOND,0);
        return calendar.getTimeInMillis()/1000;
    }

    private int getDayCode(long daySecond){
        val calendar = DateUtil.calendar(daySecond*1000);
        return Integer.parseInt(DateUtil.format(calendar.getTime(), "yyyyMMdd"));
    }

    public Blogger getBloggerByUid(long uid){
        Optional<Blogger> bloggerOptional =  bloggerRepository.findByOUid(uid);
        return bloggerOptional.isPresent()?bloggerOptional.get():null;
    }

    public Blogger save(Blogger blogger){
        return bloggerRepository.save(blogger);
    }

    /**
     * 调用接口通知更新博主信息
     * @param bloggerId
     */
    public CallBloggerUpdateResponse callUpdateBlogger(long bloggerId){
        // 返回结果
        CallBloggerUpdateResponse response = new CallBloggerUpdateResponse();
        //查询播主数据
        Optional<Blogger> blogger = bloggerRepository.findByOUid(bloggerId);
        if(blogger.isPresent()){
            //获取最新更新时间
            Date lastPubDate = blogger.get().getLastPubTime();
            //是否需要更新
            boolean needToUpdate = true;
            if(lastPubDate != null){
                //判断最后更新时间是否超过一天
                LocalDateTime lastLocalDate = lastPubDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                needToUpdate = lastLocalDate.isBefore(LocalDateTime.now().minusDays(1));
            }
            if(needToUpdate){
                //需要更新
                //更新播主记录的redis key
                String updateKey = RedisKeysManager.BLOGGER_UPDATE_KEY_PREFIX + bloggerId;
                // 更新播主记录锁的key
                String callUpdateKey = RedisKeysManager.BLOGGER_UPDATE_LOCK_KEY_PREFIX + bloggerId;
                LocalDateTime lockValue;
                //判断redis中是否已经记录过更新该播主的记录
                if(redisTemplate.opsForValue().get(updateKey) != null){
                    //24小时内已发出更新请求
                    response.setStatsCode(CallBloggerUpdateStatusEnum.UPDATING_EXISTED.getValue());
                }else{
                    lockValue = LocalDateTime.now();
                    //判断是否有锁标志，没有就添加锁，设置过期时间
                    try{
                        if(redisTemplate.opsForValue().setIfAbsent(callUpdateKey,
                                lockValue.toString(),CALL_BLOGGER_UPDATE_KEY_EXPIRED,TimeUnit.SECONDS)){
                            //调用接口添加更新博主任务进队列
                            boolean callResult = callUpdateBloggerRest(bloggerId,blogger.get().getORegion(),blogger.get().getBloggerType());
                            if(callResult){
                                //redis设置该播主进入更新队列，设置24h过期时间
                                redisTemplate.opsForValue().set(updateKey,
                                        LocalDateTime.now().toString(),BLOGGER_UPDATE_KEY_EXPIRED, TimeUnit.DAYS);
                                response.setStatsCode(CallBloggerUpdateStatusEnum.UPDATING.getValue());
                            }else{
                                response.setStatsCode(CallBloggerUpdateStatusEnum.FAILED.getValue());
                                response.setMsg("call updating blogger failed!");
                            }
                        }else{
                            //24小时内已发出更新请求
                            response.setStatsCode(CallBloggerUpdateStatusEnum.UPDATING_EXISTED.getValue());
                        }
                    }catch (Exception e){
                        log.info("call updating blogger failed! ",e);
                        response.setStatsCode(CallBloggerUpdateStatusEnum.FAILED.getValue());
                        response.setMsg("call updating blogger failed!");
                    }finally {
                        //判断是否是当前线程加的锁
                        String getLockValue = redisTemplate.opsForValue().get(callUpdateKey);
                        if(getLockValue != null && getLockValue.equals(lockValue.toString())){
                            //删除锁
                            redisTemplate.delete(callUpdateKey);
                        }
                    }
                }
                //todo 没有考虑到分布式集群redis情况，只适合单机redis；锁超时没进行处理，可能会有多个线程拿到锁；无限自选等待需要改进添加条件
//                while(true){
//                    //判断redis中是否已经记录过更新该播主的记录
//                    if(redisTemplate.opsForValue().get(updateKey) != null){
//                        //24小时内已发出更新请求
//                        response.setStatsCode(CallBloggerUpdateStatusEnum.UPDATING_EXISTED.getValue());
//                        break;
//                    }
//                    lockValue = LocalDateTime.now();
//                    //判断是否有分布式锁，有则自旋等待,没有就添加redis分布式锁，设置过期时间，否则自旋等待
//                    isLock = redisTemplate.opsForValue().setIfAbsent(callUpdateKey,
//                            lockValue.toString(),CALL_BLOGGER_UPDATE_KEY_EXPIRED,TimeUnit.SECONDS);
//                    if(isLock != null && isLock){
//                        //调用接口添加更新博主任务进队列
//                        boolean callResult = callUpdateBloggerRest(bloggerId,blogger.get().getORegion(),blogger.get().getBloggerType());
//                        if(callResult){
//                            //redis设置该播主进入更新队列，设置24h过期时间
//                            redisTemplate.opsForValue().set(updateKey,
//                                    LocalDateTime.now().toString(),BLOGGER_UPDATE_KEY_EXPIRED, TimeUnit.DAYS);
//                            response.setStatsCode(CallBloggerUpdateStatusEnum.UPDATING.getValue());
//                        }else{
//                            response.setStatsCode(CallBloggerUpdateStatusEnum.FAILED.getValue());
//                            response.setMsg("call updating blogger failed!");
//                        }
//                        //判断是否是当前线程加的锁
//                        String getLockValue = redisTemplate.opsForValue().get(callUpdateKey);
//                        if(getLockValue != null && getLockValue.equals(lockValue.toString())){
//                            //删除分布式锁
//                            redisTemplate.delete(callUpdateKey);
//                        }
//                        break;
//                    }
//                }
            }else{
                //未超一天不需更新
                response.setStatsCode(CallBloggerUpdateStatusEnum.NOT_UPDATE.getValue());
            }
        }else{
            //数据库中未找到
            response.setStatsCode(CallBloggerUpdateStatusEnum.OTHER.getValue());
            response.setMsg("not find the blogger!");
        }
        return response;
    }


    /**
     * 调用接口添加更新博主任务进队列
     * @param bloggerId
     * @param region
     * @param bloggerType
     * @return
     */
    private boolean callUpdateBloggerRest(long bloggerId,String region, String bloggerType){
        //调用接口添加更新博主任务进队列
        boolean result = true;
        String uri = TIKTOK_DATA_WEB_HOST + "/blogger?bloggerId={bloggerId}&region={region}&bloggerType={bloggerType}";
        try{
            ResponseEntity<Boolean> responseEntity = restTemplate.getForEntity(uri,Boolean.class,bloggerId,region,bloggerType);
            if(responseEntity.getStatusCode() != HttpStatus.OK
                    || responseEntity.getBody() == null
                    || responseEntity.getBody() == false) {
                result = false;
                log.info(String.format("调用接口添加更新博主任务失败. status: %s . msg: %s uri: %s",
                        responseEntity.getStatusCodeValue(), responseEntity.getBody(),
                        uri.replace("{bloggerId}",String.valueOf(bloggerId))));
            }
        }catch (Exception e){
            log.info(String.format("调用更新博主任务接口链接失败. uri: %s",
                    uri.replace("{bloggerId}",String.valueOf(bloggerId))
                            .replace("{region}",region != null ? region : "null")
                            .replace("{bloggerType}",bloggerType != null ? bloggerType : "null")),e);
            result = false;
        }
        return result;
    }

    /**
     * 获取博主粉丝画像数据
     * @param bloggerId
     * @return
     */
    public BloggerFansPortraitResponse getBloggerFansPortrait(long bloggerId){
        //结果
        BloggerFansPortraitResponse bloggerFansPortraitResponse;
        //博主粉丝画像记录的redis key
        String bloggerFansPortraitKey = RedisKeysManager.BLOGGER_FANS_PORTRAIT_KEY_PREFIX + bloggerId;
        //redis返回结果json
        String bloggerFansPortraitJson = redisTemplate.opsForValue().get(bloggerFansPortraitKey);
        if(StringUtils.isEmpty(bloggerFansPortraitJson)){
            //从es获取数据
            SearchRequest searchRequest = new SearchRequest(ESUtil.BLOGGER_INDEX);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders.matchQuery("uid",bloggerId));
            searchRequest.source(searchSourceBuilder);
            SearchHit[] hits;
            try{
                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
                hits = searchResponse.getHits().getHits();
            } catch (IOException e) {
                log.error("es search error : {}",e.getMessage());
                hits = null;
            }
            if(hits != null && hits.length > 0){
                String s = hits[0].getSourceAsString();
                bloggerFansPortraitResponse = JsonUtil.jsonParse(BloggerFansPortraitResponse.class,hits[0].getSourceAsString());
                if(bloggerFansPortraitResponse != null){
                    //数据存到redis中
                    redisTemplate.opsForValue().set(bloggerFansPortraitKey,
                            JsonUtil.parseJson(bloggerFansPortraitResponse),
                            BLOGGER_FANS_PORT_KEY_EXPIRED,TimeUnit.DAYS);
                }else{
                    bloggerFansPortraitResponse = getEmptyBloggerFansPortrait(bloggerFansPortraitKey);
                }
            }else{
                // 返回空对象，同时往redis里设置1个缓存
                bloggerFansPortraitResponse = getEmptyBloggerFansPortrait(bloggerFansPortraitKey);
            }
        }else{
            bloggerFansPortraitResponse = JsonUtil.jsonParse(BloggerFansPortraitResponse.class,bloggerFansPortraitJson);
        }

        //todo 下面这种方式没有考虑到分布式集群redis情况，只适合单机redis；锁超时没进行处理，可能会有多个线程拿到锁；无限自选等待需要改进添加条件
//
//        //查询博主粉丝画像记录锁的 key
//        String bloggerFansPortraitLockKey = RedisKeysManager.BLOGGER_FANS_PORT_LOCK_KEY_PREFIX + bloggerId;
//        //是否有redis锁
//        Boolean isLock;
//        //锁的值，用来解锁时判断是否是当前线程加的锁
//        LocalDateTime lockValue;
//        //自旋等待
//        while(true){
//            //redis 查询
//            bloggerFansPortraitJson = redisTemplate.opsForValue().get(bloggerFansPortraitKey);
//            if(StringUtils.isEmpty(bloggerFansPortraitJson)){
//                lockValue = LocalDateTime.now();
//                //如果没有,查询是否有redis锁,无redis锁设置redis锁,有则自旋等待
//                isLock = redisTemplate.opsForValue().setIfAbsent(bloggerFansPortraitLockKey,
//                        lockValue.toString(),BLOGGER_FANS_PORT_EMPTY_KEY_EXPIRED,TimeUnit.SECONDS);
//                if(isLock != null && isLock){
//                    //拿到锁的去数据库中查询
//                    Optional<BloggerFansPortrait> bloggerFansPortrait = bloggerFansPortraitRepository.findByUid(bloggerId);
//                    //数据存在
//                    if(bloggerFansPortrait.isPresent()){
//                        //数据存到redis中
//                        bloggerFansPortraitResponse = bloggerFansPortraitMapper.mapper(bloggerFansPortrait.get());
//                        redisTemplate.opsForValue().set(bloggerFansPortraitKey,
//                                JsonUtil.parseJson(bloggerFansPortraitResponse),
//                                BLOGGER_FANS_PORT_KEY_EXPIRED,TimeUnit.DAYS);
//                    }else{
//                        // 数据不存在，调用接口
//                        callUpdateBloggerFansPortrait(bloggerId);
//                        // 返回空对象，同时往redis里设置1个缓存
//                        bloggerFansPortraitResponse = new BloggerFansPortraitResponse();
//                        redisTemplate.opsForValue().set(bloggerFansPortraitKey,
//                                JsonUtil.parseJson(bloggerFansPortraitResponse),
//                                BLOGGER_FANS_PORT_LOCK_KEY_EXPIRED,TimeUnit.HOURS);
//                    }
//                    String getLockValue = redisTemplate.opsForValue().get(bloggerFansPortraitLockKey);
//                    //判断锁是否存在且是当前线程加的锁
//                    if(getLockValue != null && getLockValue.equals(lockValue.toString())){
//                        //删除redis锁
//                        redisTemplate.delete(bloggerFansPortraitLockKey);
//                    }
//                    break;
//                }
//            }else{
//                bloggerFansPortraitResponse = JsonUtil.jsonParse(BloggerFansPortraitResponse.class,bloggerFansPortraitJson);
//                break;
//            }
//        }
        return bloggerFansPortraitResponse;
    }

    /**
     * 返回空结果
     * @param redisKey
     * @return
     */
    public BloggerFansPortraitResponse getEmptyBloggerFansPortrait(String redisKey){
        BloggerFansPortraitResponse bloggerFansPortraitResponse = new BloggerFansPortraitResponse();
        redisTemplate.opsForValue().set(redisKey,
                JsonUtil.parseJson(bloggerFansPortraitResponse),
                BLOGGER_FANS_PORT_EMPTY_KEY_EXPIRED,TimeUnit.HOURS);
        return bloggerFansPortraitResponse;
    }
}
